package com.blank.white.common.exception;

import com.alibaba.fastjson.JSONException;
import com.blank.white.common.ResponseEntity;
import com.blank.white.common.util.BaseUtil;
import com.blank.white.common.util.LoggerUtil;
import com.blank.white.common.util.context.ContextUtil;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;

import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * @desc 描述一下该类的作用
 * @classname GlobalExceptionHandler
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 运行时异常
     *
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(value = AuthException.class)
    public ResponseEntity authHandle(AuthException e, HttpServletRequest request) {
        ResponseEntity result = ResponseEntity.fail(e.getError().getCode(),
                ContextUtil.getTraceId(),
                e.getError().getMessage(), e.getData());
        log.warn(LoggerUtil.format("AuthException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }


    /**
     * json入参类型校验
     *
     * @return
     */
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public ResponseEntity jsonParamCheck(HttpMessageNotReadableException e, HttpServletRequest request) {
        ResponseEntity<String> result = ResponseEntity.fail(ErrorsEnum.ERROR_PARAM.getCode(),
                ContextUtil.getTraceId(),
                ErrorsEnum.ERROR_PARAM.getMessage());
        log.warn(LoggerUtil.format("handleHttpMessageNotReadableException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }

    /**
     * 运行时异常
     *
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(value = BaseException.class)
    public ResponseEntity defaultErrorHandler(BaseException e, HttpServletRequest request) {
        return handleBaseException(e);
    }

    /**
     * json入参类型校验
     *
     * @return
     */
    @ExceptionHandler(value = JSONException.class)
    public ResponseEntity jsonParamCheck(JSONException e, HttpServletRequest request) {
        ResponseEntity<String> result = ResponseEntity.fail(ErrorsEnum.ERROR_PARAM.getCode(),
                ContextUtil.getTraceId(),
                ErrorsEnum.ERROR_PARAM.getMessage());
        log.warn(LoggerUtil.format("handleJSONException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }

    /**
     * json入参类型校验
     *
     * @return
     */
    @ExceptionHandler(value = IllegalArgumentException.class)
    public ResponseEntity illegalArgumentCheck(IllegalArgumentException e, HttpServletRequest request) {
        ResponseEntity<String> result = ResponseEntity.fail(ErrorsEnum.ERROR_PARAM.getCode(),
                ContextUtil.getTraceId(),
                ErrorsEnum.ERROR_PARAM.getMessage());
        log.warn(LoggerUtil.format("handleIllegalArgumentException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }



    /**
     * 请求头Content type不支持
     *
     * @param e       请求头Content type不支持
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)
    public ResponseEntity defaultErrorHandler(HttpMediaTypeNotSupportedException e, HttpServletRequest request) {
        ResponseEntity<String> result = ResponseEntity.fail(ErrorsEnum.REQUEST_TYPES_NOT_SUPPORTED.getCode(),
                ContextUtil.getTraceId(),
                ErrorsEnum.REQUEST_TYPES_NOT_SUPPORTED.getMessage());
        log.warn(LoggerUtil.format("handleHttpMediaTypeNotSupportedException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }

    /**
     * 请求方法不支持
     *
     * @param e       请求方法不支持
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public ResponseEntity defaultErrorHandler(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        ResponseEntity<String> result = ResponseEntity.fail(ErrorsEnum.REQUEST_METHOD_NOT_SUPPORTED.getCode(),
                ContextUtil.getTraceId(),
                ErrorsEnum.REQUEST_METHOD_NOT_SUPPORTED.getMessage());
        log.warn(LoggerUtil.format("handleHttpMediaTypeNotSupportedException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }

    /**
     * 参数缺失
     *
     * @param e       参数缺失
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ResponseEntity defaultErrorHandler(MissingServletRequestParameterException e, HttpServletRequest request) {
        return handleParamException(e, e.getMessage(), "handleMissingServletRequestParameterException ErrorResponse=[{}]");
    }

    /**
     * 参数缺失
     *
     * @param e       文件参数缺失
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = MissingServletRequestPartException.class)
    public ResponseEntity defaultErrorHandler(MissingServletRequestPartException e, HttpServletRequest request) {
        return handleParamException(e, e.getMessage(), "handleMissingServletRequestPartException ErrorResponse=[{}]");
    }

    /**
     * 参数缺失
     *
     * @param e       文件参数缺失
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity defaultErrorHandler(MethodArgumentNotValidException e, HttpServletRequest request) {
        BindingResult result = e.getBindingResult();
        // 遍历出校验不通过的变量名及其校验信息，拼接成错误信息
        List<FieldError> errors = result.getFieldErrors();
        StringBuilder sb = new StringBuilder();
        for (FieldError error : errors) {
            sb.append(error.getField()).append(":").append(error.getDefaultMessage()).append(" ");
        }
        return handleParamException(e, sb.toString(), "handleMethodArgumentNotValidException ErrorResponse=[{}]");
    }

    /**
     * 参数格式转化错误
     *
     * @param e       参数格式转化错误
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = BindException.class)
    public ResponseEntity defaultErrorHandler(BindException e, HttpServletRequest request) {
        StringBuilder stringBuilder = bindErrorParam(e);
        ResponseEntity<Object> result = ResponseEntity.fail(ErrorsEnum.ERROR_PARAM.getCode(),
                ContextUtil.getTraceId(), stringBuilder.toString());
        log.warn(LoggerUtil.format("handleBindException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }



    /**
     * 客户端终止操作
     *
     * @param e       客户端终止操作
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = ClientAbortException.class)
    public ResponseEntity defaultErrorHandler(ClientAbortException e, HttpServletRequest request) {
        ResponseEntity<String> result = ResponseEntity.success(null, ContextUtil.getTraceId());
        log.warn(LoggerUtil.format("handleClientAbortException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }


    /**
     * 参数绑定异常
     *
     * @param e       参数格式转化错误
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = InvalidFormatException.class)
    public ResponseEntity defaultErrorHandler(InvalidFormatException e, HttpServletRequest request) {
        ResponseEntity<String> result = ResponseEntity.fail(ErrorsEnum.ERROR_PARAM.getCode(),
                ContextUtil.getTraceId(),
                ErrorsEnum.ERROR_PARAM.getMessage());
        log.warn(LoggerUtil.format("handleInvalidFormatException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }

    /**
     * 其他未捕获异常
     *
     * @param e       其他未捕获异常
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = Throwable.class)
    public ResponseEntity unknownErrorHandler(Throwable e, HttpServletRequest request) {
        return handleThrowable(e, e.toString(), ErrorsEnum.ERROR_SYSTEM,
                "ExceptionHandler ErrorResponse=[{}]", HttpStatus.OK);
    }

    /**
     * 数据库异常
     *
     * @param e       参数格式转化错误
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = DuplicateKeyException.class)
    public ResponseEntity defaultErrorHandler(DuplicateKeyException e, HttpServletRequest request) {
        ResponseEntity<String> result = ResponseEntity.fail(ErrorsEnum.RETRY_AGAIN.getCode(),
                ContextUtil.getTraceId(),
                ErrorsEnum.RETRY_AGAIN.getMessage());
        log.warn(LoggerUtil.format("handleDuplicateKeyException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }

    /**
     * 业务异常
     *
     * @param e       参数格式转化错误
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = BusinessException.class)
    public ResponseEntity businessExceptionErrorHandler(BusinessException e, HttpServletRequest request) {
        ResponseEntity<String> result = ResponseEntity.fail(e.getError().getCode(),
                ContextUtil.getTraceId(),
                e.getError().getMessage(),
                e.getData());
        log.warn(LoggerUtil.format("businessExceptionErrorHandler ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }

    private ResponseEntity handleThrowable(Throwable throwable, String resultMessage, ErrorsEnum errors, String logMessage, HttpStatus internalServerError) {
        ResponseEntity<Object> result = ResponseEntity.fail(errors.getCode(), ContextUtil.getTraceId(), errors.getMessage());
        log.error(LoggerUtil.format(logMessage, resultMessage, result, throwable));
        return result;
    }

    /**
     * 三方接口调用异常
     *
     * @param e       参数格式转化错误
     * @param request 请求记录日志
     * @return ResponseEntity
     */
    @ExceptionHandler(value = RpcException.class)
    public ResponseEntity defaultErrorHandler(RpcException e, HttpServletRequest request) {
        ResponseEntity<String> result = ResponseEntity.fail(ErrorsEnum.THIRD_ERROR.getCode(),
                ContextUtil.getTraceId(), ErrorsEnum.THIRD_ERROR.getMessage());
        log.error(LoggerUtil.format("handleRpcInvokeException ErrorResponse=[{}], params=[{}]",
                result, e.getMessage(), e));
        return result;
    }

    private ResponseEntity handleParamException(Throwable ex, String resultMessage, String logMessage) {
        ResponseEntity<Object> result = ResponseEntity.fail(ErrorsEnum.ERROR_PARAM.getCode(),
                ContextUtil.getTraceId(), resultMessage);
        log.warn(LoggerUtil.format(logMessage, resultMessage, result, ex));
        return result;
    }

    private static ResponseEntity handleBaseException(BaseException ex) {
        ResponseEntity<Object> result = ResponseEntity.fail(ex.getError().getCode(), ContextUtil.getTraceId(), BaseUtil.isNullOrEmpty(ex.getMsg()) ? ex.getMessage() : ex.getMsg());
        log.warn(LoggerUtil.format("handleBaseException errorMsg:[{}], cause by:[{}], exceptionData:[{}] ErrorResponse=[{}], params=[{}]",
                ex.toString(), ExceptionUtils.getRootCause(ex), ex.getData(), result, ex.getData(), ex));
        return result;
    }

    private StringBuilder bindErrorParam(BindException throwable) {
        List<ObjectError> list = throwable.getAllErrors();
        StringBuilder sb = new StringBuilder("参数错误：[");
        for (ObjectError item : list) {
            sb.append(item.getDefaultMessage()).append(',');
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append(']');
        return sb;
    }
}
